﻿using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using Conducido.WcfMessageLogger.Services.Behaviors.Entities;
using Conducido.WcfMessageLogger.Services.Behaviors.Entities.Config;

namespace Conducido.WcfMessageLogger.Services.Behaviors.Logger
{
    internal class LogOperator : IDisposable
    {
        private Message _currentMessage;
        private MessageBuffer _orginalBuffer;
        private Guid _messageId;
        private CorrelatedInformation _correlatedInformation;
        private string _messageXml;
        private MessageLogConfiguration _messageLogConfiguration;
        private CorrelationConfiguration _correlationConfiguration;

        /// <summary>
        /// Mostly used in request or vice versa if it's a client call
        /// </summary>
        /// <param name="currentMessage"></param>
        /// <param name="messageLogConfiguration"></param>
        public LogOperator(Message currentMessage, MessageLogConfiguration messageLogConfiguration)
        {
            _currentMessage = currentMessage;
            _messageId = Guid.NewGuid();
            _messageLogConfiguration = messageLogConfiguration;
        }

        /// <summary>
        /// Mostly used in request or vice versa if it's a client call
        /// </summary>
        /// <param name="currentMessage"></param>
        /// <param name="correlationConfiguration"></param>
        public LogOperator(Message currentMessage, CorrelationConfiguration correlationConfiguration)
        {
            _currentMessage = currentMessage;
            _messageId = Guid.NewGuid();
            _correlationConfiguration = correlationConfiguration;
        }

        /// <summary>
        /// Used in response (or request if it's a client call
        /// </summary>
        /// <param name="currentMessage"></param>
        /// <param name="correlatedInformation"></param>
        /// <param name="messageLogConfiguration"></param>
        public LogOperator(Message currentMessage, CorrelatedInformation correlatedInformation,
                           MessageLogConfiguration messageLogConfiguration)
        {
            _currentMessage = currentMessage;
            _messageId = Guid.NewGuid();
            _correlatedInformation = correlatedInformation;
            _messageLogConfiguration = messageLogConfiguration;
        }

        private void CreateBufferedCopy()
        {
            _orginalBuffer = _currentMessage.CreateBufferedCopy(int.MaxValue);
        }

        /// <summary>
        /// Gets the xml in string from the message
        /// </summary>
        public string MessageXml
        {
            get
            {
                if (string.IsNullOrEmpty(_messageXml))
                {
                    var xml = _orginalBuffer.CreateNavigator();
                    _messageXml = xml.OuterXml;
                    return _messageXml;
                }
                return _messageXml;
            }

        }

        /// <summary>
        /// Logs the incoming message to the database
        /// </summary>
        /// <param name="timestamp"></param>
        /// <param name="isClientCall"></param>
        public void LogIncoming(DateTime timestamp, bool isClientCall)
        {
            CreateBufferedCopy(); //TODO: Create property

            var logger = new LogMessage(OperationContext.Current, _messageLogConfiguration);
            var correlationId = CorrelationContext.Current.CorrelationId; //TODO: Property??
            Task.Factory.StartNew(
                () => logger.Log(timestamp, MessageXml, Direction.In, _messageId, correlationId, isClientCall));
        }

        public void LogOutgoing(DateTime timestamp, bool isClientCall)
        {
            CreateBufferedCopy(); //TODO: Create property

            if (!isClientCall)
            {
                _messageId = _correlatedInformation.MessageId;
            }
            var correlationId = CorrelationContext.Current.CorrelationId;
            var logger = new LogMessage(OperationContext.Current, _messageLogConfiguration);

            Task.Factory.StartNew(() => logger.Log(timestamp, MessageXml, Direction.Out, _messageId,
                                                   correlationId, isClientCall));
        }

        /// <summary>
        /// If a message is being read. You have to 'rewind' it and return a 'clean' message back.
        /// </summary>
        /// <returns></returns>
        public Message CopyMessage()
        {
            if (_orginalBuffer != null)
            {
                return _orginalBuffer.CreateMessage();
            }
            return _currentMessage.CreateBufferedCopy(int.MaxValue).CreateMessage();
        }

        /// <summary>
        /// Created correlated information which can be used for the response
        /// </summary>
        /// <returns></returns>
        public CorrelatedInformation CreateCorrelatedInformation()
        {
            var info = new CorrelatedInformation();
            info.MessageId = _messageId;
            info.Operationname = "TODO:!"; //TODO: Create
            info.Servicename = "TODO:!"; //TODO: Create

            return info;
        }

        /// <summary>
        /// Closes the buffer
        /// </summary>
        public void Dispose()
        {
            if (_orginalBuffer != null)
            {
                _orginalBuffer.Close();
            }
            if (_currentMessage != null)
            {
                _currentMessage.Close();
            }
        }

        private Guid ReadHeader()
        {
            string headerName = _correlationConfiguration.ClassName;
            string headerNamespace = _correlationConfiguration.HeaderNamespace;
            int headerIndex = _currentMessage.Headers.FindHeader(headerName, headerNamespace);
            string headerAssemblyName = _correlationConfiguration.AssemblyName;
            string headerTypeName = _correlationConfiguration.FullyQualifiedTypeName;
            string propertyName = _correlationConfiguration.PropertyName;
            var headerAssembly = Assembly.Load(headerAssemblyName); //TODO: What if signed?
            var headerType = headerAssembly.GetType(headerAssemblyName + ".DataContracts.MyHeader");

            var list = _currentMessage.Headers.ToList();
            int counter = 0;
            bool foundHeader = false;
            foreach (var messageHeaderInfo in list)
            {
                if (messageHeaderInfo.Name == headerName)
                {
                    foundHeader = true;
                    break;
                }
                counter++;
            }
            if (foundHeader)
            {
                var xmlDictReader = _currentMessage.Headers.GetReaderAtHeader(counter);

                DataContractSerializer dcs = new DataContractSerializer(headerType);
                var result = dcs.ReadObject(xmlDictReader);

                PropertyInfo idProperty = result.GetType().GetProperty(propertyName, typeof (Guid));
                var idValue = idProperty.GetValue(result, null);
                Guid guid = Guid.Parse(idValue.ToString());

                if (guid != Guid.Empty)
                {
                    return guid;
                }
            }
            //Guid correlationId = (Guid) id;

            return Guid.NewGuid();
        }

        public void AddCorrelationToContext()
        {
            var id = ReadHeader();
            //Read from class
            CorrelationContext.Current.Initialize(id);
        }

        /// <summary>
        /// Do take in mind that the assigned property to be set. Should be string or Guid!
        /// </summary>
        internal void AddCorrelationToConfiguredHeader()
        {
            string headerName = _correlationConfiguration.ClassName;
            string headerNamespace = _correlationConfiguration.HeaderNamespace;
            int headerIndex = _currentMessage.Headers.FindHeader(headerName, headerNamespace);
            string headerAssemblyName = _correlationConfiguration.AssemblyName;
            string headerTypeName = _correlationConfiguration.FullyQualifiedTypeName;
            string propertyName = _correlationConfiguration.PropertyName;
            var headerAssembly = Assembly.Load(headerAssemblyName); //TODO: What if signed?
            var headerType = headerAssembly.GetType(headerAssemblyName + ".DataContracts.MyHeader");
            //TODO: We should have this value somewhere!!!

            var list = _currentMessage.Headers.ToList();
            int counter = 0;
            bool foundHeader = false;
            foreach (var messageHeaderInfo in list)
            {
                if (messageHeaderInfo.Name == headerName)
                {
                    foundHeader = true;
                    break;
                }
                counter++;
            }

            if (foundHeader)
            {
                var xmlDictReader = _currentMessage.Headers.GetReaderAtHeader(counter);

                DataContractSerializer dcs = new DataContractSerializer(headerType);
                var result = dcs.ReadObject(xmlDictReader);
                PropertyInfo idProperty = result.GetType().GetProperty(propertyName);
                if (idProperty.PropertyType == typeof (string))
                {

                }

                if (idProperty.PropertyType == typeof (Guid))
                {
                    idProperty.SetValue(result, CorrelationContext.Current.CorrelationId, null);
                }

                MessageHeader newMessageHeader =
                    MessageHeader.CreateHeader(headerName, headerNamespace,
                                               result);

                _currentMessage.Headers.RemoveAt(headerIndex);
                _currentMessage.Headers.Add(newMessageHeader);
            }
        }
    }
}

